// Copyright 2011 Google Inc. All Rights Reserved.
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.

//
//  IOSObjectArray.m
//  JreEmulation
//
//  Created by Tom Ball on 9/9/11.
//

#import "IOSObjectArray.h"
#import "java/lang/AssertionError.h"

@implementation IOSObjectArray

@synthesize elementType = elementType_;

- (id)initWithLength:(NSUInteger)length {
  @throw [[[JavaLangAssertionError alloc]
          initWithNSString:@"type argument not specified"] j2objc_autorelease];
  return nil;
}

+ (id)arrayWithLength:(NSUInteger)length type:(IOSClass *)type {
  return [[[self alloc] initWithLength:length
                                  type:type] j2objc_autorelease];
}

// Initializes buffer_ with a CFMutableArray.  This array has a fixed size like
// Java, done by setting its maximum size.  If no objects are specified, the
// array slots are initialized to null, which also provides access to the slot
// regardless of whether it holds an id or not.
- (id)initWithLength:(NSUInteger)length type:(IOSClass *)type {
  if ((self = [super initWithLength:length])) {
    elementType_ = type;
#if ! __has_feature(objc_arc)
    [elementType_ retain];
#endif
  }
  return self;
}

+ (id)arrayWithObjects:(const id *)objects
                 count:(NSUInteger)count
                  type:(IOSClass *)type {
  return [[[self alloc] initWithObjects:objects
                                  count:count
                                  type:type] j2objc_autorelease];
}

+ (id)arrayWithType:(IOSClass *)type count:(int)count args:(va_list)args {
  if (count > 0) {
    __unsafe_unretained id *objects =
        (__unsafe_unretained id *) calloc(count, sizeof(id));
    for (int i = 0; i < count; i++) {
      objects[i] = va_arg(args, id);
    }
    id array = [[self class] arrayWithObjects:objects
                                        count:count
                                         type:type];
    free(objects);
    return array;
  } else {
    return [[self class] arrayWithObjects:nil count:0 type:type];
  }
}

- (id)initWithObjects:(const id *)objects
                count:(NSUInteger)count
                 type:(IOSClass *)type {
  if ((self = [self initWithLength:count type:type])) {
    if (NULL != objects) {
      id *buffer = (id *)self.storage.mutableBytes;
      for (NSUInteger i = 0; i < count; i++) {
        // TODO: Has to transfer ownership under ARC, or add reference under GC;
        //       but seems it doesn't because of raw pointers asignment
        id element = objects[i];
#if ! __has_feature(objc_arc)
        [element retain];
#endif
        buffer[i] = element;
      }
    }
  }
  return self;
}

+ (id)arrayWithType:(IOSClass *)type count:(int)count, ... {
  va_list args;
  va_start(args, count);
  id result = [self arrayWithType:type count:count args:args];
  va_end(args);
  return result;
}

+ (id)arrayWithClass:(Class)clazz count:(int)count, ... {
  va_list args;
  va_start(args, count);
  id result = [self arrayWithType:[IOSClass classWithClass:clazz]
                            count:count
                             args:args];
  va_end(args);
  return result;
}

+ (id)arrayWithArray:(IOSObjectArray *)array {
  return [IOSObjectArray arrayWithObjects:(id *)array.storage.bytes
                                    count:array.count
                                     type:array.elementType];
}

+ (id)arrayWithNSArray:(NSArray *)array type:(IOSClass *)type {
  NSUInteger count = [array count];
  id *objects = malloc(sizeof(id) * count);
  [array getObjects:objects range:NSMakeRange(0, count)];
  id result = [IOSObjectArray arrayWithObjects:objects count:count type:type];
  free(objects);
  return result;
}

+ (id)arrayWithDimensions:(NSUInteger)dimensionCount
                  lengths:(NSUInteger *)dimensionLengths
                     type:(IOSClass *)type {

  // If dimension of 1, just return a regular array of objects.
  if (dimensionCount == 1) {
    NSUInteger size = *dimensionLengths;
    IOSObjectArray *result = [[[self class] alloc] initWithLength:size
                                                             type:type];
    return [result j2objc_autorelease];
  }

  // Create an array of arrays, which is recursive to handle additional
  // dimensions.
  NSUInteger arraySize = *dimensionLengths;
  IOSObjectArray *result =
      [[IOSObjectArray alloc] initWithLength:arraySize type:
          [IOSClass classWithClass:[IOSObjectArray class]]];
  result->elementType_ = type;
#if ! __has_feature(objc_arc)
  [result->elementType_ retain];
#endif
  for (NSUInteger i = 0; i < arraySize; i++) {
    id subarray = [[self class] arrayWithDimensions:dimensionCount - 1
                                            lengths:dimensionLengths + 1
                                               type:type];
    [result replaceObjectAtIndex:i withObject:subarray];
  }

  return [result j2objc_autorelease];
}

- (id)objectAtIndex:(NSUInteger)index {
  [self checkIndex:index];
  
  const id *buffer = (const id *)self.storage.bytes;
  return buffer[index];
}

- (id)replaceObjectAtIndex:(NSUInteger)index withObject:(id)value {
  [self checkIndex:index];
  id *buffer = (id *)self.storage.mutableBytes;
  // TODO: Has to transfer ownership under ARC, or remove reference under GC;
  //       but seems it doesn't because of raw pointers asignment
#if ! __has_feature(objc_arc)
  id prev = buffer[index];
  [prev autorelease];
  [value retain];
#endif
  buffer[index] = value;
  return value;
}

- (void)getObjects:(NSObject **)buffer length:(NSUInteger)length {
  [self checkIndex:(length - 1)];
  
  const id *storageBuffer = (const id *)self.storage.bytes;
  for (NSUInteger i = 0; i < length; i++) {
    // TODO: Has to transfer ownership under ARC, or add reference under GC;
    //       but seems it doesn't because of raw pointers asignment
    id element = storageBuffer[i];
#if ! __has_feature(objc_arc)
    [element retain];
#endif
    buffer[i] = element;
  }
}

- (void) arraycopy:(NSRange)sourceRange
       destination:(IOSArray *)destination
            offset:(NSInteger)offset {
  [self checkRange:sourceRange];
  NSUInteger count = sourceRange.length;
  [destination checkRange:NSMakeRange(offset, count)];

  // Do ranges overlap?
  const id *buffer = (const id *)self.storage.bytes;
  id *destinationBuffer = (id *)destination.storage.mutableBytes;
  if (self == destination && sourceRange.location < offset) {
    for (int i = sourceRange.length - 1; i >= 0; i--) {
      // TODO: Has to transfer ownership under ARC, or references under GC;
      //       but seems it doesn't because of raw pointers asignment
      id newElement = buffer[i + sourceRange.location];
#if ! __has_feature(objc_arc)
      id oldElement = destinationBuffer[i + offset];
      [oldElement autorelease];
      [newElement retain];
#endif
      destinationBuffer[i + offset] = newElement;
    }
  } else {
    // TODO: Has to transfer ownership under ARC, or references under GC;
    //       but seems it doesn't because of raw pointers asignment
    for (NSUInteger i = 0; i < sourceRange.length; i++) {
      id newElement = buffer[i + sourceRange.location];
#if ! __has_feature(objc_arc)
      id oldElement = destinationBuffer[i + offset];
      [oldElement autorelease];
      [newElement retain];
#endif
      destinationBuffer[i + offset] = newElement;
    }
  }
}

- (id)copyWithZone:(NSZone *)zone {
  IOSObjectArray *result =
      [[IOSObjectArray allocWithZone:zone] initWithLength:self.count
                                                     type:self.elementType];
  id *buffer = (id *)result.storage.mutableBytes;
  const id *selfBuffer = (const id *)self.storage.bytes;
  
  for (NSUInteger i = 0, count = self.count; i < count; i++) {
    // TODO: Has to transfer ownership under ARC, or references under GC;
    //       but seems it doesn't because of raw pointers asignment
    id element = selfBuffer[i];
#if ! __has_feature(objc_arc)
    [element retain];
#endif
    buffer[i] = element;
  }
  return result;
}

- (NSString *)descriptionOfElementAtIndex:(NSUInteger)index {
  return (NSString *)[[self objectAtIndex:index] description];
}

+ (NSUInteger)elementLength
{
  return sizeof(id *);
}

#if ! __has_feature(objc_arc)

- (void)dealloc {
  [elementType_ release];
  id *buffer = (id *)self.storage.mutableBytes;
  for (NSUInteger i = 0, count = self.count; i < count; i++) {
    [buffer[i] release];
  }
  
  [super dealloc];
}

#endif

@end
